/**
 * 数组原型方法，写原型方法时都先使用 Object(this) 创建一个新的引用
 * Object()详解：1.Object的构造函数，在非构造函数上下文中调用时， Object 和 new Object()表现一致
 * 2.如果给定的值是 null 或 undefined, 它会创建并返回一个空对象
 * 3.如果是基础类型，将返回一个和给定的值相对应的类型的对象，相当于使用对应类型的包装对象构造函数new调用得到的对象 例：Object(1) => Number {1}
 * 4.如果给定值是一个已经存在的对象，会返回这个对象的引用(Object(o) === o 得到 true)
 */

/**
 * push: 从末端插入一个或多个元素，返回新数组长度
 * @param {item} 任意类型，任意多个
 * @return 新数组的长度
 */
Array.prototype.fakePush = function(...items) {
  let O = Object(this);
  // 无符号右移获取当前数组和传入项当成的数组长度，保证拿到的长度为数字且为整数
  let len = O.length >>> 0;
  let argCount = items.length >>> 0;
  // 2 ** 53 - 1 为 JS 能表示的最大正整数
  if (len + argCount > 2 ** 53 - 1) {
    throw new TypeError('The number of array is over the max value restricted!');
  }
  for (let i = 0; i < argCount; i++) {
    O[len + i] = items[i];
  }
  let newLength = len + argCount;
  O.length = newLength;
  return newLength;
}

// pop：从末端删除一个元素，返回被删除的项
Array.prototype.fakePop = function() {
  let O = Object(this);
  let len = O.length >>> 0;
  // 处理数组长度为0的情况
  if (len === 0) {
    O.length = 0;
    return undefined;
  }
  // 取最后一个数据作为返回值，修改数组长度
  let value = O[len - 1];
  delete O[len - 1];
  len--;
  O.length = len;
  return value;
}

// shift：从头部删除一个元素，返回被删除的项
Array.prototype.fakeShift = function() {
  // let temp = this[0]
  // for (let i = 0; i < this.length - 1; i++) {
  //   this[i] = this[i + 1]
  // }
  // this.length && this.length--
  // return temp
  // 相当于换一个引用，不使用this操作当前调用方法的数组
  let O = Object(this);
  let len = O.length >>> 0;
  if (len === 0) {
    O.length = 0;
    return undefined;
  }
  let value = O[0];
  for (let i = 0; i < len; i++) {
    O[i] = O[i + 1];
  }
  len--;
  O.length = len;
  return value;
}

// unshift: 从头部插入一个或多个元素，返回新数组长度
Array.prototype.fakeUnshift = function(...items) {
  // let argLen = arguments.length
  // // 原来的元素全部向后移动 n(传入参数长度)位
  // for (let i = argLen + this.length - 1; i >= argLen; i--) {
  //   this[i] = this[i - argLen]
  // }
  // // 前几位顺序填充
  // for(let i = 0; i < argLen; i++) {
  //   this[i] = arguments[i]
  // }
  // return this.length
  let O = Object(this);
  let len = O.length >>> 0;
  let argLen = items.length >>> 0;
  let newLength  = len + argLen;
  // 1.插入后长度溢出(浏览器中控制台可设置数组的长度大于这个值，但是设置之后数组的长度会变0，应该是当成了类数组)
  if (newLength > 2 ** 53 - 1) {
    throw new TypeError('The number of array is over the max value restricted!');
  }
  // 2.先把原来的每一项都往后移 argLen 位
  for (let i = newLength - 1; i >= argLen; i--) {
    O[i] = O[i - argLen];
  }
  // 3.处理前面一段
  for (let i = 0; i < argLen; i++) {
    O[i] = items[i];
  }
  return newLength;
}

/**
 * concat：拼接两个或多个数组/值，返回拼接后的结果(副本)
 * 1、没有给concat方法传递参数的情况下，它只是复制当前数组并返回副本。
 * 2、如果传递的是一或多个数组，则该方法会将这些数组扁平化后的每一项都添加到结果数组中
 * 3、如果传递的值是不是数组，这些值会被简单的添加到数组的末尾
*/
Array.prototype.fakeConcat = function(...items) {
  // let result = []
  // // 先拷贝一份当前数组
  // for(let i = 0; i < this.length; i++) {
  //   result[i] = this[i]
  // }
  // // 拼接参数中的值，区分数组和值
  // for (let i = 0; i < arguments.length; i++) {
  //   if (arguments[i] instanceof Array) {
  //     for (let j = 0; j < arguments[i].length; j++) {
  //       result[result.length] = arguments[i][j]
  //     }
  //   } else {
  //     result[result.length] = arguments[i]
  //   }
  // }
  // return result
  let O = Object(this);
  // 1.拿到原数组和参数数组的长度
  let len = O.length >>> 0;
  let argLen = items.length >>> 0;
  // 2.先拷贝一份原数组
  for(let i = 0; i < len; i++) {
    result[i] = O[i];
  }
  // 3.遍历传入的数组，判断每一项是否为数组，是数组需要展开一层，不是的话就直接加到数组末尾
  for(let j = 0; j < argLen; j++) {
    if (Array.isArray(O[j])) {
      for (let k = 0; k < argLen; k++) {
        result[result.length] = O[j][k];
      }
    } else {
      result[result.length] = O[j];
    }
  }
  return result;
}

/**
 * 模拟splice 通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。
 * @param {Number} start 指定修改的开始位置（从0计数）。
 * 1.如果超出了数组的长度，则从数组末尾开始添加内容；
 * 2.如果是负值，-n是倒数第n个元素并且等价于array.length-n位置开始删除并替换；
 * 3.如果负数的绝对值大于数组的长度，则表示开始位置为第0位。
 * @param {Number} deleteCount (可选) 表示要移除的数组元素的个数。
 * 1.如果 deleteCount 大于 start 之后的元素的总数，则从 start 后面的元素都将被删除（含第 start 位）。
 * 2.如果 deleteCount 被省略了，或者它大于或者等于start之后的所有元素的数量，那么start之后数组的所有元素都会被删除。
 * 3.如果 deleteCount 是 0 或者负数，则不移除元素。
 * @param {Any} (item1,item2,... 可选) 
 * 1.要添加进数组的元素，从start 位置开始。
 * 2.如果不指定，则 splice() 将只删除数组元素。
 * @rerun {Array} 由被删除的元素组成的一个数组。如果没有删除元素，则返回空数组。
 * 注意：此方法会修改原数组，返回的是被删除项组成的数组
 */
Array.prototype.fakeSplice = function (startIndex, deleteCount, ...addElements) {
  // 1.获取参数的个数，用于判断做出相应的操作
  let argumentsLen = arguments.length >>> 0;
  // 1.0没传参直接返回空数组
  if (argumentsLen === 0) return [];
  // 2.拿到数组的长度
  let array = Object(this);
  let len = array.length >>> 0;
  
  // 处理起始索引为负数、超出数组长度的情况，返回处理后的下标
  const computerStartIndex = (startIndex, len) => {
    if (typeof startIndex !== 'number') {
      return 0;
    }
    if (startIndex < 0) {
      return startIndex + len > 0 ? startIndex + len : 0;
    }
    return startIndex >= len ? len : startIndex;
  };
  
  // 处理删除元素个数的参数的特殊情况
  const computeDeleteCount = (startIndex, len, deleteCount, argumentsLen) => {
    // case1: 删除数目没有传，默认删除startIndex及后面所有的，startIndex >= len 时就不删，只添加
    if (argumentsLen === 1) {
      return len - startIndex;
    }

    // case2: 删除数目为负数或者非数值
    if (typeof deleteCount !== 'number' || deleteCount < 0) {
      return 0;
    }
    // case3: 删除数目过大
    if (deleteCount > len - startIndex) {
      return len - startIndex;
    }
    return deleteCount;
  };
  // 3.参数的清洗工作
  startIndex = computerStartIndex(startIndex, len);
  deleteCount = computeDeleteCount(startIndex, len, deleteCount, argumentsLen);

  // 4.处理删除个数之后再新建一个待删除元素的数组
  let deleteArr = new Array(deleteCount);

  // 5.判断 sealed 对象 和 frozen 对象，即密封对象和冻结对象
  if (Object.isSealed(array) && deleteCount !== addElements.length) {
    // 只能修改
    throw new TypeError("the object is a sealed object!");
  } else if (
    Object.isFrozen(array) &&
    (deleteCount > 0 || addElements.length > 0)
  ) {
    // 不可修改，因此删除和增加都不能大于0
    throw new TypeError("the object is a frozen object!");
  }

  // 拷贝被删除元素
  const sliceDeleteElements = (array, startIndex, deleteCount, deleteArr) => {
    for (let i = 0; i < deleteCount; i++) {
      let index = startIndex + i;
      // 删除的数组去空
      if (index in array) {
        let current = array[index];
        deleteArr[i] = current;
      }
    }
  };
  
  // 重点：移动元素
  const movePostElements = (array, startIndex, len, deleteCount, addElements) => {
    if (deleteCount === addElements.length) {
      return;
    } else if (deleteCount > addElements.length) {
      // 删除的元素比新增的元素多，那么后面的元素整体向前挪动
      // 一共需要挪动len - startIndex - deleteCount个元素
      for (let i = startIndex + deleteCount; i < len; i++) {
        let fromIndex = i;
        // 将要挪动到的目标位置
        let toIndex = i - (deleteCount - addElements.length);
        // 空元素也要挪动
        if (fromIndex in array) {
          array[toIndex] = array[fromIndex];
        } else {
          delete array[toIndex];
        }
      }
      // 后面的元素向前挪，相对于数组长度减小了，需要删除冗余元素
      // 目前长度为len + addElements - deleteCount
      for (let i = len - 1; i >= len + addElements.length - deleteCount; i--) {
        delete array[i];
      }
    } else if (deleteCount < addElements.length) {
      // 删除的元素比新增的元素少，那么后面的元素整体向后挪动
      // 为什么从后向前遍历，因为从前向后前面的元素会覆盖后一个元素。
      for (let i = len - 1; i >= startIndex + deleteCount; i--) {
        let fromIndex = i;
        // 将要挪动到的目标位置
        let toIndex = i + (addElements.length - deleteCount);
        if (fromIndex in array) {
          array[toIndex] = array[fromIndex];
        } else {
          delete array[toIndex];
        }
      }
    }
  };

  // 6.拷贝删除的元素
  sliceDeleteElements(array, startIndex, deleteCount, deleteArr);
  // 7.移动删除元素后面的元素
  movePostElements(array, startIndex, len, deleteCount, addElements);

  // 8.遍历插入新元素
  for (let i = 0; i < addElements.length; i++) {
    array[startIndex + i] = addElements[i];
  }

  // 9.修改原数组长度
  array.length = len - deleteCount + addElements.length;

  // 10.返回被删除的元素数组
  return deleteArr;
};


/**
 * 数组扁平化 多维数组 => 一维数组
 * @param {Number} depth 可选 表示拉平的层数
 * 不传默认拉平一层；Infinity 关键字作为参数时，无论多少层嵌套，都会转为一维数组；传<=0参数，表示不拉平
 * 如果原数组有空位，flat()方法会跳过空位
 */
Array.prototype.fakeFlat = function (depth = 1) {
  // 1.拿到调用的数组副本
  let arr = this.concat();
  if (typeof depth === 'number' && depth > 0) {
    // 2.1深度大于0的情况
    while (depth > 0) {
      // 3.1数组中还有数组元素的话并且 depth > 0，继续展开一层数组 
      if (arr.some(item => Array.isArray(item))) {
        // 4. concat 展开一层 
        arr = [].concat.apply([], arr);
      } else {
        // 3.2数组中没有数组元素并且不管 depth 是否依旧大于 0，停止循环
        break;
      }
      depth--;
    }
  }
  // 5. 如果展开的深度不是数值或者不大于0的数值，不拉平但是去除空位，深度大于0时也给最后的结果去空
  let result = [];
  arr.forEach(item => item !== void 0 && result.push(item));
  // let O = Object(arr);
  // let len = O.length >>> 0;
  // let resLen = 0;
  // for (let k = 0; k < len; k++) {
  //   if (k in O) {
  //     result[resLen++] = O[k];
  //   }
  // }
  return result;
}

/**
 * 模拟map: 创建一个新的列表，这个新的列表由原来列出的所有元素都需要一次提供函数后的返回值组成
 * @param {Function} callbackfn 回调函数 接收三个参数 currentValue[, index[, array] 当前元素[, 当前元素索引[, 调用map的数组]]
 * @param {Object} thisArg 执行回调函数值被使用 this
 * 注意：遍历的时候会跳过空值，但是返回数组的长度和原数组一样不会缺少空值的项
 */
Array.prototype.fakeMap = function (callbackfn, thisArg) {
  // 1.处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'map' of null or undefined`);
  }
  // 2.处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) !== '[object Function]') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  // 3.使用Object构造函数创建一个 this 的新引用
  let obj = Object(this);
  // 4.无符号右移保证 len 为数字且是整数，然后重新创建一个长度为 len 的数组
  let len = obj.length >>> 0;
  let newArray = new Array(len);
  for (let k = 0; k < len; k++) {
    // 5.查找有效属性，处理稀疏数组的情况
    if (k in obj) {
      // 6.依次传入 this，当前项，当前索引，整个数组
      let mappedValue = callbackfn.call(thisArg, obj[k], k, obj);
      newArray[k] = mappedValue;
    }
  }
  return newArray;
}

/**
 * 模拟reduce: 逐个遍历当前元素，每一步都将元素的计算结果相加（上一步的计算结果是当前所有元素的总和）——直到没有更多的元素被相加，返回最终汇总结果
 * @param {Function} callbackfn 回调函数 
 * 接收四个参数 previousValue, currentValue[, currentIndex[, array] 上一次调用callbackfn的值，当前元素[, 当前元素索引[, 调用reduce的数组]]
 * @param {*} initialValue 任何类型
 * 核心要点，处理初始值不传的情况，回调函数的参数，处理返回值
 */
Array.prototype.fakeReduce = function (callbackfn, initialValue) {
  // 1.处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'reduce' of null or undefined`);
  }
  // 2.处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != '[object Function]') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  // 3.使用Object构造函数创建一个 this 的新引用
  let obj = Object(this);
  // 4.无符号右移保证 len 为数字且是整数
  let len = obj.length >>> 0;
  let k = 0;
  // 5.设置累加器初始值
  let accumulator = initialValue;
  // 核心1：处理不传初始值的情况
  if (accumulator === undefined) {
    for(; k < len; k++) {
      // 这里会过滤掉空值，第一次调用时把累加器的值设置为第一个非空元素
      if (k in obj) {
        accumulator = obj[k];
        console.log(accumulator);
        k++;
        break;
      }
    }
  }
  // 6.数组全为空的情况并且没有给初始值
  if (k === len && accumulator === undefined) throw new Error("Each element of the array is empty");
  // 核心2：处理返回值
  for (; k < len; k++) {
    // 7.查找有效属性，处理稀疏数组的情况(这里会过滤掉空值)
    if (k in obj) {
      // 8.依次传入 this，当前项，当前索引，整个数组
      accumulator = callbackfn.call({}, accumulator, obj[k], k, obj);
      console.log(accumulator);
    }
  }
  return accumulator;
}

/**
 * 模拟filter: 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素
 * @param {Function} callbackfn 回调函数 
 * 接收三个参数 element[, index[, array]] 当前元素[, 当前元素索引[, 调用filter的数组]]
 * @param {Object} thisArg 执行回调函数值被使用 this
 * 注意：没有任何数组元素通过测试，则返回空数组。callbackfn 只会在已经赋值的索引上被调用。
 */
Array.prototype.fakeFilter = function (callbackfn, thisArg) {
  // 1.处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'filter' of null or undefined`);
  }
  // 2.处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != '[object Function]') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  // 3.使用Object构造函数创建一个 this 的新引用
  let O = Object(this);
  // 4.无符号右移保证 len 为数字且是整数
  let len = O.length >>> 0;
  // 5.存储通过测试的元素的数组及其长度
  let result = [];
  let resLen = 0;
  for (let k = 0; k < len; k++) {
    // 6.查找有效属性，处理空值
    if (k in O) {
      // 7.调用回调函数，如果返回 true 就把当前项添加到新数组
      if (callbackfn.call(thisArg, O[k], k, O)) {
        // result.push(O[k]);
        result[resLen++] = O[k];
      }
    }
  }
  return result;
}

/**
 * 模拟find: 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
 * @param {Function} callbackfn 回调函数 
 * 接收三个参数 element[, index[, array]] 当前元素[, 当前元素索引[, 调用find的数组]]
 * @param {Object} thisArg 执行回调函数值被使用 this
 * 注意：1.callback 函数会为数组中的每个索引调用即从 0 到 length - 1，而不仅仅是那些被赋值的索引
 * 2.这意味着对于稀疏数组来说，该方法的效率要低于那些只遍历有值的索引的方法
 */
Array.prototype.fakeFind = function (callbackfn, thisArg) {
  // 1.处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'find' of null or undefined`);
  }
  // 2.处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != '[object Function]') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  // 3.使用Object构造函数创建一个 this 的新引用
  let O = Object(this);
  // 4.无符号右移保证 len 为数字且是整数
  let len = O.length >>> 0;
  let result;
  for (let k = 0; k < len; k++) {
    // 5.调用回调函数 注意：不过滤空值
    // 如果返回 true 就把当前项作为返回值，否则返回undefined
    console.log(k, O[k]);
    if (callbackfn.call(thisArg, O[k], k, O)) {
      result = O[k];
      break;
      // 6.findIndex此处就为 result = k;
    }
  }
  return result;
}

/**
 * 模拟some: 测试数组中是不是至少有1个元素通过了被提供的函数测试，返回一个 Boolean。
 * @param {Function} callbackfn 回调函数 
 * 接收三个参数 element[, index[, array]] 当前元素[, 当前元素索引[, 调用some的数组]]
 * @param {Object} thisArg 执行回调函数值被使用 this
 * 注意：1.callback 函数只会在那些被赋值的索引上被调用，找到一个符合条件的立即返回true，否则返回false
 */
Array.prototype.fakeSome = function (callbackfn, thisArg) {
  // 1.处理数组类型异常
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'some' of null or undefined`);
  }
  // 2.处理回调类型异常
  if (Object.prototype.toString.call(callbackfn) != '[object Function]') {
    throw new TypeError(callbackfn + ' is not a function');
  }
  // 3.使用Object构造函数创建一个 this 的新引用
  let O = Object(this);
  // 4.无符号右移保证 len 为数字且是整数
  let len = O.length >>> 0;
  let result = false;
  // 扩展：对于 every() 这里设置为 true
  // let result = true;
  for (let k = 0; k < len; k++) {
    // 5.过滤空值
    if (k in O) {
      console.log(k, O[k]);
      // 6.如果有一个返回 true 就停止循环
      if (callbackfn.call(thisArg, O[k], k, O)) {
        result = true;
        break;
      }
      // 7.对于every这里刚好相反
      // if (!callbackfn.call(thisArg, O[k], k, O)) {
      //   result = false;
      //   break;
      // }
    }
  }
  return result;
}

/**
 * 模拟join: 方法将一个数组（或一个类数组对象）的所有元素连接成一个字符串并返回这个字符串。
 * @param {String} separator 可选，拼接的分隔符，默认为','
 * 注意：1.空数组返回空字符串
 * 2.某个元素为null 或者 undefined，此元素会转换为空字符串
 * 3.数组长度为1不拼接分隔符，直接把数组元素转成 String 返回
 */
Array.prototype.fakeJoin = function (separator = ',') {
  // 0.处理数组类型异常，只判断 null/undefined，是防止 Object(null) 创建一个空对象
  if (this === null || this === undefined) {
    throw new TypeError(`Cannot read property 'find' of null or undefined`);
  }
  // 1.先处理空数组
  let O = Object(this);
  let len = O.length >>> 0;
  // 这里给空字符串很重要
  let result = '';
  if (len === 0) {
    return result;
  }
  function isNullOrUndefined(value) {
    return value === null || value === undefined
  }
  // 2. 处理数组长度为1的时候，null 和 undefined，返回空字符串
  if (len === 1) {
    return isNullOrUndefined(O[0]) ? '' : result + O[0];
  }
  // 3.先处理分割符，类型为非字符串时，需要转换成字符串(使用''+separator就可以实现)
  let sep = '' + separator;
  let k = 0;
  // 4.循环拼接除最后一项的所有项
  for (; k < len - 1; k++) {
    // 5.拼接每一项和分隔符，注意：null 和 undefined 需要转换成空字符串
    if (isNullOrUndefined(O[k])) {
      result += sep;
    } else {
      result = result + O[k] + sep;
    }
  }
  // 6.拼接最后一项
  if (!isNullOrUndefined(O[k])) result += O[k];
  return result;
}
